package xfuzzy.alg_genetico.indeterminado.cross;

import java.util.ArrayList;
import java.util.List;

import xfuzzy.xfghl.codification.ChromosomeFixer;
import xfuzzy.xfghl.codification.DoubleChromosome;
import xfuzzy.xfghl.codification.Encoder;
import xfuzzy.xfghl.model.XfghlConfig;
import xfuzzy.xfhl.model.hierStruct.XfhlINode;
import xfuzzy.xfhl.model.hierStruct.XfhlModule;
import xfuzzy.xfhl.model.hierStruct.XfhlNode;

public final class CrossoverGP2 extends Crossover {
	
	public CrossoverGP2(long seed)  {
		
		super(seed);
	}

	@Override
	public List<DoubleChromosome> cruzar(DoubleChromosome padre1, DoubleChromosome padre2, XfghlConfig config) {

		List<DoubleChromosome> offspring = this.cruzarEstructura(padre1, padre2, config);
		ChromosomeFixer.reparar(offspring.get(0), config);
		ChromosomeFixer.reparar(offspring.get(1), config);
		return offspring;
	}

	protected List<DoubleChromosome> cruzarEstructura(DoubleChromosome padre1, DoubleChromosome padre2, XfghlConfig config) {

		DoubleChromosome child1 = null;
		DoubleChromosome child2 = null;
		List<DoubleChromosome> childs = new ArrayList<DoubleChromosome>();
		double probRandom = this.random.nextDouble();
		int variablesPadre1 = -1, variablesPadre2 = -1;
		//nuevo.setVariables(hijo.getVariables());
		XfhlModule moduloPadre1 = Encoder.decodeBackwardAnchuraSimple(padre1);
		XfhlModule moduloPadre2 = Encoder.decodeBackwardAnchuraSimple(padre2);
		//XfhlModuleToGraph.createGraph(moduloPadre1, "cruceGP_padre1");
		//XfhlModuleToGraph.createGraph(moduloPadre2, "cruceGP_padre2");
		double [] prob = new double[padre1.getNumModulos()];
		int indice = 0;
		XfhlModule actual = null;
		XfhlModule seleccionadoPadre1 = null;
		XfhlModule seleccionadoPadre2 = null;
		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
		modulos.add(moduloPadre1);
		// se guardan en una lista todos los modulos de la configuracion padre1
		while (indice < modulos.size())  {
			actual = modulos.get(indice);
			if (actual.getEnt2() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt2());
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt1());
			}
			// comprobamos que si es un nodo terminal, se le de menos probabilidad
			if ((actual.getEnt2() instanceof XfhlNode) && (actual.getEnt1() instanceof XfhlNode))  {
				prob[indice] = 1.0 / padre1.getNumModulos();
			}
			else  
				prob[indice] = 1.0;
			indice++;
		}
		//while (seleccionadoPadre2 == null)  {
		// se elige un modulo del padre 1 aleatoriamente (los primeros modulos tienen mas probabilidad)
		int indiceRandom = this.random.nextInt(modulos.size());
		while (probRandom > prob[indiceRandom])  {
			indiceRandom = this.random.nextInt(modulos.size());
		}
		seleccionadoPadre1 = modulos.get(indiceRandom);
		// se cuenta el numero de variables que dependen de este modulo
		variablesPadre1 = this.cuentaVariablesRama(seleccionadoPadre1);
		modulos = new ArrayList<XfhlModule>();
		modulos.add(moduloPadre2);
		indice = 0;
		// se guardan en una lista todos los modulos de la configuracion padre2
		while (indice < modulos.size())  {
			actual = modulos.get(indice);
			if (actual.getEnt2() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt2());
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt1());
			}
			indice++;
		}
		// se busca el modulo del que dependan el mismo numero de variables
		for (int indicePadre2 = 0; indicePadre2 < modulos.size(); indicePadre2++)  {
			 actual = modulos.get(indicePadre2);
			 variablesPadre2 = this.cuentaVariablesRama(actual);
			 if (variablesPadre1 == variablesPadre2)  {
				 seleccionadoPadre2 = actual;
				 break;
			 }
		}
		
		if (seleccionadoPadre2 == null)  {
			
		}
	
		XfhlINode auxiliarEntrada1 = null;
		XfhlINode auxiliarEntrada2 = null;
		auxiliarEntrada1 = seleccionadoPadre1.getEnt1();
		auxiliarEntrada2 = seleccionadoPadre1.getEnt2();
		seleccionadoPadre1.setEnt1(seleccionadoPadre2.getEnt1());
		seleccionadoPadre1.setEnt2(seleccionadoPadre2.getEnt2());
		seleccionadoPadre2.setEnt1(auxiliarEntrada1);
		seleccionadoPadre2.setEnt2(auxiliarEntrada2);
		child1 = Encoder.codeBackwardAnchura(moduloPadre1, padre1.getNumVariables());
		child2 = Encoder.codeBackwardAnchura(moduloPadre2, padre2.getNumVariables());
		child1.setEvaluated(false);
		child2.setEvaluated(false);
		childs.add(child1);
		childs.add(child2);
		return childs;
	}
	
	private int cuentaVariablesRama(XfhlModule modulo)  {
		
		int variablesRama = 0;
		List<XfhlModule> modulosPendientes = new ArrayList<XfhlModule>();
		XfhlModule actual = null;
		modulosPendientes.add(modulo);
		while (modulosPendientes.size() > 0)  {
			actual = modulosPendientes.remove(0);
			if (actual.getEnt2() instanceof XfhlModule)  {
				modulosPendientes.add((XfhlModule) actual.getEnt2());
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				modulosPendientes.add((XfhlModule) actual.getEnt1());
			}
			if (actual.getEnt2() instanceof XfhlNode)  {
				variablesRama++;
			}
			if (actual.getEnt1() instanceof XfhlNode)  {
				variablesRama++;
			}
		}
		return variablesRama;
	}
}
